/* https://www.nesdev.org/wiki/Mapper */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "NesCPU6502.h"
#include "NesPPU.h"
#include "NesSystem.H"
#include "NesMapper.h"



NesMapperInofType g_typeNesMapperInfo = {0};



void vNesMapper0Set(uint16_t usAddr, uint8_t ucValue)
{
    /* 无操作 */
}

void vNesMapper1Set(uint16_t usAddr, uint8_t ucValue)
{
    static uint32_t st_uiRegister = 0;
    static uint8_t st_ucControl = 0x0C, st_ucMask = 0x01;

    /* bit7为1，则需要复位 */
    if(ucValue & 0x80)
    {
        st_ucControl |= 0x0C;
        st_uiRegister = 0;
        st_ucMask = 0x01;
        return;
    }
    else
    {
        /* LSB first 循环移位5个bit */
        st_uiRegister |= (ucValue & 1) ? st_ucMask : 0;
        st_ucMask <<= 1;
        if(st_ucMask != 0x20)
            return;
    }

    /* addr / 0x2000 : 8kByte */
    switch(usAddr >> 13)
    {
        /* 0x8000 - 0x8FFF */
        case 4 :
            st_ucControl = st_uiRegister;
            switch(st_ucControl & 0x03)
            {
                case 0: vNesMirrorTypeSet(MIRROR_TYPE_ONELOW); break;
                case 1: vNesMirrorTypeSet(MIRROR_TYPE_ONEUPPER); break;
                case 2: vNesMirrorTypeSet(MIRROR_TYPE_VERTICAL); break;
                case 3: vNesMirrorTypeSet(MIRROR_TYPE_HORIZONTAL); break;
            }
            break;

        /* 0xA000 - 0xBFFF */
        case 5 :
            /* bit4：0 表示1次性交换 8KB 的 CHR，1 表示交换 前面 4KB CHR banks */
            if(st_ucControl & 0x10)
            {
                /* st_uiRegister * 1024 * 4 */
                cNesPPUBankAddr4kByteSet(0, st_uiRegister << 12);
            }
            else
            {
                /* (st_uiRegister & 0xFE) * 1024 * 8 */
                cNesPPUBankAddr8kByteSet((st_uiRegister & 0xFE) << 13);
            }
            break;

        /* 0xC000 - 0xDFFF */
        case 6 :
            /* bit4：0 表示1次性交换 8KB 的 CHR（此处需忽略），1 表示交换 后面 4KB CHR banks */
            if(st_ucControl & 0x10)
            {
                /* st_uiRegister * 1024 * 4 */
                cNesPPUBankAddr4kByteSet(1, st_uiRegister << 12);
            }
            break;

        /* 0xE000 - 0xFFFF */
        case 7 :
            st_uiRegister &= 0x0F;
            /* bit3：0 表示 bank 大小为 32KB，也就是1次性交换 32KB(0x8000-0xFFFF)，1 表示按照 bit2 的设置来交换 */
            if(st_ucControl & 0x08)
            {
                /* bit2：0 表示 bank switching 发生在 0xC000-0xFFFF，1 表示 bank switching 发生在 0x8000-0xBFFF */
                if(st_ucControl & 0x04)
                {
                    /* st_uiRegister * 1024 * 16 */
                    cNesCPUBankAddr16kByteSet(0, st_uiRegister << 14);

                    /* 固定为最后1个Bank */
                    cNesCPUBankAddr16kByteSet(1, g_typeNesInfo.PRG_ROMSize - 1024 * 16);

                }
                else
                {
                    /* 固定为第1个Bank */
                    cNesCPUBankAddr16kByteSet(0, 0);

                    /* st_uiRegister * 1024 * 16 */
                    cNesCPUBankAddr16kByteSet(1, st_uiRegister << 14);
                }
            }
            else
            {
                /* (st_uiRegister & 0x0E) * 1024 * 32 */
                cNesCPUBankAddr32kByteSet((st_uiRegister & 0x0E) << 15);
            }
            break;

        default : return;
    }

    /* 复位寄存器 */
    st_uiRegister = 0;
    st_ucMask = 0x01;
}

/*
7  bit  0
---- ----
xxxx pPPP
     ||||
     ++++- Select 16 KB PRG ROM bank for CPU $8000-$BFFF
          (UNROM uses bits 2-0; UOROM uses bits 3-0)
*/
void vNesMapper2Set(uint16_t usAddr, uint8_t ucValue)
{
    /* 向 0x8000-0xFFFF 这部分地址空间的任一地址写入 PRG bank number 即可选择1个 PRG bank 映射到CPU前16k地址 */
    /* ucValue * 1024 * 16 */
    cNesCPUBankAddr16kByteSet(0, (uint32_t)ucValue << 14);
}

/*
7  bit  0
---- ----
cccc ccCC
|||| ||||
++++-++++- Select 8 KB CHR ROM bank for PPU $0000-$1FFF
*/
void vNesMapper3Set(uint16_t usAddr, uint8_t ucValue)
{
    cNesPPUBankAddr8kByteSet((uint32_t)ucValue << 13);
}

void vNesMapper4Set(uint16_t usAddr, uint8_t ucValue)
{
    static uint8_t st_ucRegister8000 = 0, st_ucCurrentRegister = 0;
    static uint8_t st_ucIrqReloadValue = 0, st_ucIrqReload = 0;

    usAddr &= 0xE001;
    switch(usAddr)
    {
        /* 0x8000 - 0x9FFF */
        case 0x8000 : st_ucRegister8000 = ucValue; st_ucCurrentRegister = ucValue & 0x07; break;
        /* 0x8001 - 0x9FFF */
        case 0x8001 :
            /* CHR A12 inversion (0: two 2 KB banks at $0000-$0FFF, four 1 KB banks at $1000-$1FFF;
                                  1: two 2 KB banks at $1000-$1FFF, four 1 KB banks at $0000-$0FFF) */
            /* PRG ROM bank mode (0: $8000-$9FFF swappable, $C000-$DFFF fixed to second-last bank;
                                  1: $C000-$DFFF swappable, $8000-$9FFF fixed to second-last bank) */
            switch(st_ucCurrentRegister)
            {
                case 0: vNesPPUMemoryLoad((st_ucRegister8000 & 0x80) ? 4 : 0, (uint32_t)(ucValue & 0xFE) << 10, 2048); break;

                case 1: vNesPPUMemoryLoad((st_ucRegister8000 & 0x80) ? 6 : 2, (uint32_t)(ucValue & 0xFE) << 10, 2048); break;

                case 2: vNesPPUMemoryLoad((st_ucRegister8000 & 0x80) ? 0 : 4, (uint32_t)ucValue << 10, 1024); break;

                case 3: vNesPPUMemoryLoad((st_ucRegister8000 & 0x80) ? 1 : 5, (uint32_t)ucValue << 10, 1024); break;

                case 4: vNesPPUMemoryLoad((st_ucRegister8000 & 0x80) ? 2 : 6, (uint32_t)ucValue << 10, 1024); break;

                case 5: vNesPPUMemoryLoad((st_ucRegister8000 & 0x80) ? 3 : 7, (uint32_t)ucValue << 10, 1024); break;

                case 6: cNesCPUBankAddr8kByteSet((st_ucRegister8000 & 0x40) ? 2 : 0, (uint32_t)ucValue << 13); break;

                case 7: cNesCPUBankAddr8kByteSet((st_ucRegister8000 & 0x40) ? 1 : 1, (uint32_t)ucValue << 13); break;

                default : break;
            }

            if(st_ucRegister8000 & 0x40)
            {
                cNesCPUBankAddr8kByteSet(0, g_typeNesInfo.PRG_ROMSize - 1024 * 16);
                cNesCPUBankAddr8kByteSet(3, g_typeNesInfo.PRG_ROMSize - 1024 * 8);
            }
            else
            {
                cNesCPUBankAddr8kByteSet(2, g_typeNesInfo.PRG_ROMSize - 1024 * 16);
                cNesCPUBankAddr8kByteSet(3, g_typeNesInfo.PRG_ROMSize - 1024 * 8);
            }
            break;

        /* 0xA000 - 0xBFFF: 屏幕镜像 */
        case 0xA000 : vNesMirrorTypeSet((ucValue & 1) ? MIRROR_TYPE_HORIZONTAL : MIRROR_TYPE_VERTICAL); return;
        /* 0xA001 - 0xBFFF: 内存保护（无需实现） */
        case 0xA001 : return;

        /* 0xC000 - 0xDFFF */
        case 0xC000 : st_ucIrqReloadValue = ucValue; return;
        /* 0xC001 - 0xDFFF */
        case 0xC001 : st_ucIrqReload = 1; return;

        /* 0xE000 - 0xFFFF: 禁止中断 */
        case 0xE000 : g_typeNesInfo.ptypeCPUInfo->IRQFlag = FALSE; return;
        /* 0xE001 - 0xFFFF: 使能中断 */
        case 0xE001 : g_typeNesInfo.ptypeCPUInfo->IRQFlag = TRUE; return;

        default : return;
    }
}

/*
7  bit  0
---- ----
xxxM xPPP
   |  |||
   |  +++- Select 32 KB PRG ROM bank for CPU $8000-$FFFF
   +------ Select 1 KB VRAM page for all 4 nametables
*/
void vNesMapper7Set(uint16_t usAddr, uint8_t ucValue)
{
    cNesCPUBankAddr32kByteSet((uint32_t)(ucValue & 7) << 15);
}

uint32_t st_uiNesMapper9CHRAddr[4];
uint8_t st_ucNesMapper9Latch[2] = {1, 1};

void vNesMapper9PPUHook(uint32_t uiAddr, uint32_t uiValue)
{
    switch (uiAddr)
    {
        case 0x0FD8: st_ucNesMapper9Latch[0] = 0; cNesPPUBankAddr4kByteSet(0, st_uiNesMapper9CHRAddr[0]); break;
        case 0x0FE8: st_ucNesMapper9Latch[0] = 1; cNesPPUBankAddr4kByteSet(0, st_uiNesMapper9CHRAddr[1]); break;
        case 0x1FD8: st_ucNesMapper9Latch[1] = 0; cNesPPUBankAddr4kByteSet(1, st_uiNesMapper9CHRAddr[2]); break;
        case 0x1FE8: st_ucNesMapper9Latch[1] = 1; cNesPPUBankAddr4kByteSet(1, st_uiNesMapper9CHRAddr[3]); break;

        default: break;
    }
}

void vNesMapper9Set(uint16_t usAddr, uint8_t ucValue)
{
    switch(usAddr & 0xF000)
    {
        /* 0xA000 - 0xAFFF */
        case 0xA000 : cNesCPUBankAddr8kByteSet(0, (uint32_t)(ucValue & 0x0F) << 13); break;

        /* 0xB000 - 0xBFFF */
        case 0xB000 : st_uiNesMapper9CHRAddr[0] = (uint32_t)(ucValue & 0x1F) << 12; cNesPPUBankAddr4kByteSet(0, st_uiNesMapper9CHRAddr[st_ucNesMapper9Latch[0]]); break;
        /* 0xC000 - 0xCFFF */
        case 0xC000 : st_uiNesMapper9CHRAddr[1] = (uint32_t)(ucValue & 0x1F) << 12; cNesPPUBankAddr4kByteSet(0, st_uiNesMapper9CHRAddr[st_ucNesMapper9Latch[0]]); break;
        /* 0xD000 - 0xDFFF */
        case 0xD000 : st_uiNesMapper9CHRAddr[2] = (uint32_t)(ucValue & 0x1F) << 12; cNesPPUBankAddr4kByteSet(1, st_uiNesMapper9CHRAddr[st_ucNesMapper9Latch[1] + 2]); break;
        /* 0xE000 - 0xEFFF */
        case 0xE000 : st_uiNesMapper9CHRAddr[3] = (uint32_t)(ucValue & 0x1F) << 12; cNesPPUBankAddr4kByteSet(1, st_uiNesMapper9CHRAddr[st_ucNesMapper9Latch[1] + 2]); break;

        /* 0xF000 - 0xFFFF */
        case 0xF000 : vNesMirrorTypeSet((ucValue & 1) ? MIRROR_TYPE_VERTICAL : MIRROR_TYPE_HORIZONTAL); break;

        default : break;
    }
}

void vNesMapper10Set(uint16_t usAddr, uint8_t ucValue)
{
    switch(usAddr & 0xF000)
    {
        /* 0xA000 - 0xAFFF */
        case 0xA000 : cNesCPUBankAddr16kByteSet(0, (uint32_t)(ucValue & 0x0F) << 14); break;

        default : vNesMapper9Set(usAddr, ucValue); break;
    }
}

/*
7  bit  0
---- ----
CCCC LLPP
|||| ||||
|||| ||++- Select 32 KB PRG ROM bank for CPU $8000-$FFFF
|||| ++--- Used for lockout defeat
++++------ Select 8 KB CHR ROM bank for PPU $0000-$1FFF
*/
void vNesMapper11Set(uint16_t usAddr, uint8_t ucValue)
{
    cNesCPUBankAddr32kByteSet((uint32_t)(ucValue & 3) << 15);

    cNesPPUBankAddr8kByteSet((uint32_t)(ucValue >> 4) << 13);
}

/*
7  bit  0
---- ----
xxxx xxCC
       ||
       ++- Select 4 KiB CHR RAM bank for PPU $1000-$1FFF
*/
void vNesMapper13Set(uint16_t usAddr, uint8_t ucValue)
{
    cNesPPUBankAddr4kByteSet(1, (uint32_t)(ucValue >> 4) << 12);
}

void vNesMapperVRCSet(uint16_t usAddr, uint8_t ucValue)
{
    static uint16_t usCHRBankNumber[8][2] = {0};
    static uint8_t st_ucIqrAck = 0, st_ucIqrLatch = 0;

    /* addr / 0x2000 : 8kByte */
    switch(usAddr & 0xF000)
    {
        /* 0x8000 - 0x8003 */
        case 0x8000 : cNesCPUBankAddr8kByteSet(0, ucValue << 13); break;

        /* 0x9000 - 0x9003 */
        case 0x9000 :
            switch(ucValue & 3)
            {
                case 0: vNesMirrorTypeSet(MIRROR_TYPE_VERTICAL); break;
                case 1: vNesMirrorTypeSet(MIRROR_TYPE_HORIZONTAL); break;
                case 2: vNesMirrorTypeSet(MIRROR_TYPE_ONELOW); break;
                case 3: vNesMirrorTypeSet(MIRROR_TYPE_ONEUPPER); break;

                default : break;
            }
            break;

        /* 0xA000 - 0xA003 */
        case 0xA000 : cNesCPUBankAddr8kByteSet(1, ucValue << 13); break;

        /* 0xB000 - 0xB003 */
        case 0xB000 :
            usAddr &= 3;
            usCHRBankNumber[usAddr >> 1][usAddr & 1] = ucValue & 0x1F;
            vNesPPUMemoryLoad(usAddr >> 1, (uint32_t)((usCHRBankNumber[usAddr >> 1][0] & 0x0F) | ((usCHRBankNumber[usAddr >> 1][1]) << 4)) << 10, 1024);
            break;

        /* 0xB000 - 0xB003 */
        case 0xC000 :
            usAddr &= 3;
            usCHRBankNumber[2 + (usAddr >> 1)][usAddr & 1] = ucValue & 0x1F;
            vNesPPUMemoryLoad(2 + (usAddr >> 1), (uint32_t)((usCHRBankNumber[2 + (usAddr >> 1)][0] & 0x0F) | ((usCHRBankNumber[2 + (usAddr >> 1)][1]) << 4)) << 10, 1024);
            break;

        /* 0xB000 - 0xB003 */
        case 0xD000 :
            usAddr &= 3;
            usCHRBankNumber[4 + (usAddr >> 1)][usAddr & 1] = ucValue & 0x1F;
            vNesPPUMemoryLoad(4 + (usAddr >> 1), (uint32_t)((usCHRBankNumber[4 + (usAddr >> 1)][0] & 0x0F) | ((usCHRBankNumber[4 + (usAddr >> 1)][1]) << 4)) << 10, 1024);
            break;

        /* 0xB000 - 0xB003 */
        case 0xE000 :
            usAddr &= 3;
            usCHRBankNumber[6 + (usAddr >> 1)][usAddr & 1] = ucValue & 0x1F;
            vNesPPUMemoryLoad(6 + (usAddr >> 1), (uint32_t)((usCHRBankNumber[6 + (usAddr >> 1)][0] & 0x0F) | ((usCHRBankNumber[6 + (usAddr >> 1)][1]) << 4)) << 10, 1024);
            break;

        case 0xF000 :
            usAddr |= ((usAddr >> 2) & 0x03) | ((usAddr >> 4) & 0x03) | ((usAddr >> 6) & 0x03);
            usAddr &= 3;
            switch(usAddr)
            {
                case 0: st_ucIqrLatch = (st_ucIqrLatch & 0xF0) | (ucValue & 0x0F); break;
                case 1: st_ucIqrLatch = (st_ucIqrLatch & 0x0F) | (ucValue << 4); break;
                case 2: g_typeNesInfo.ptypeCPUInfo->IRQFlag = (ucValue & 0x02) ? TRUE : FALSE; st_ucIqrAck = ucValue & 1; break;
                case 3: g_typeNesInfo.ptypeCPUInfo->IRQFlag = st_ucIqrAck ? TRUE : FALSE; break;

                default: break;
            }
            break;

        default : break;
    }
}

void vNesMapperDefPPUHook(uint32_t uiAddr, uint32_t uiValue)
{

}

void vNesMapperTypeSet(uint8_t mapperType)
{
    g_typeNesMapperInfo.mapperType = mapperType;
    g_typeNesMapperInfo.vMapperPPUHook = vNesMapperDefPPUHook;

    switch(mapperType)
    {
        case 0:   g_typeNesMapperInfo.vMapperSetFunction = vNesMapper0Set; break;

        case 105: case 155:
        case 1:   g_typeNesMapperInfo.vMapperSetFunction = vNesMapper1Set; break;

        case 94: case 180:
        case 2:   g_typeNesMapperInfo.vMapperSetFunction = vNesMapper2Set; break;

        case 3:   g_typeNesMapperInfo.vMapperSetFunction = vNesMapper3Set; break;

        case 118: case 119:
        case 4:   g_typeNesMapperInfo.vMapperSetFunction = vNesMapper4Set; break;

        case 7:   g_typeNesMapperInfo.vMapperSetFunction = vNesMapper7Set; break;

        case 9:   g_typeNesMapperInfo.vMapperSetFunction = vNesMapper9Set;  g_typeNesMapperInfo.vMapperPPUHook = vNesMapper9PPUHook; break;

        case 10:  g_typeNesMapperInfo.vMapperSetFunction = vNesMapper10Set; g_typeNesMapperInfo.vMapperPPUHook = vNesMapper9PPUHook; break;

        case 21: case 22: case 23:;
        case 25:  g_typeNesMapperInfo.vMapperSetFunction = vNesMapperVRCSet; break;

        default : g_typeNesMapperInfo.vMapperSetFunction = vNesMapper0Set; break;
    }
}

NesMapperInofType *ptyeMapperInfoGet(void)
{
    return &g_typeNesMapperInfo;
}

